﻿/*  Copyright (C) 2012 AmaroK86 (marcidm 'at' hotmail 'dot' com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AmaroK86.ImageFormat
{
    public class ImageMipMapHandler
    {
        public List<ImageFile> imageList { get; private set; }

        public ImageMipMapHandler(string imageWithMipMaps)
        {
            imageList = new List<ImageFile>();
            string fileFormat = Path.GetExtension(imageWithMipMaps);
            string fileName = Path.GetFileNameWithoutExtension(imageWithMipMaps);
            ImageFile imageMipMap;
            int headerSize;

            switch (fileFormat)
            {
                case ".dds":
                    headerSize = 128;
                    imageMipMap = new DDS(imageWithMipMaps);
                    break;
                case ".tga":
                    headerSize = 18;
                    imageMipMap = new TGA(imageWithMipMaps);
                    break;
                default: throw new FormatException("Invalid image format");
            }
            //Console.WriteLine("Image Format: {0}", imageMipMap.format);

            //check if image has mipmaps
            using (FileStream imageStream = File.OpenRead(imageWithMipMaps))
            {
                if (imageStream.Length - headerSize != ImageMipMapDataSize(imageMipMap.imgSize, imageMipMap.format, imageMipMap.BPP))
                {
                    throw new FormatException("The image doesn't have any mipmaps");
                }
                //Console.WriteLine("bytes in file: {0}, bytes calulated: {1}, BPP: {2}", imageStream.Length - headerSize, ImageMipMapDataSize(imageMipMap.imgSize, imageMipMap.format, imageMipMap.BPP), imageMipMap.BPP);
            }

            byte[] buffer = null;

            // add the first tga image
            if (fileFormat == ".tga")
            {
                //buffer = new byte[imageMipMap.imgData.Length];
                buffer = imageMipMap.imgData;
                //imageList.Add(imageMipMap);
            }

            int maxCount = (int)Math.Min(imageMipMap.imgSize.width, imageMipMap.imgSize.height);
            int count = 1;
            int imgDataPos = 0;
            while (count <= maxCount)
            {
                ImageFile newImageFile;
                ImageSize newImageSize = imageMipMap.imgSize / count;

                if (newImageSize.width <= 4 && newImageSize.height <= 4)
                    break;

                int imgDataSize = (int)ImageDataSize(newImageSize, imageMipMap.format, imageMipMap.BPP);

                if (fileFormat == ".dds")
                {
                    buffer = new byte[imgDataSize];
                    Buffer.BlockCopy(imageMipMap.imgData, imgDataPos, buffer, 0, imgDataSize);
                    imgDataPos += imgDataSize;

                    newImageFile = new DDS(fileName + "_" + newImageSize + fileFormat, newImageSize, imageMipMap.format, buffer);
                }
                else if (fileFormat == ".tga")
                {
                    newImageFile = new TGA(fileName + "_" + newImageSize + fileFormat, newImageSize, imageMipMap.format, buffer);
                    if(newImageSize != new ImageSize(1,1))
                        buffer = ShrinkImage(buffer, newImageSize, imageMipMap.BPP);
                }
                else
                    throw new FormatException("Invalid image format");

                imageList.Add(newImageFile);

                count *= 2;
            }

        }

        public void SaveAll()
        {
            foreach (ImageFile imgFile in imageList)
            {
                using (FileStream imgStream = File.OpenWrite(imgFile.fileName))
                {
                    byte[] buffer = imgFile.ToArray();
                    imgStream.Write(buffer, 0, buffer.Length);
                }
            }
        }

        private long ImageDataSize(ImageSize imgsize, string format, float BytesPerPixel)
        {
            long totalBytes = (long)((float)(imgsize.width * imgsize.height) * BytesPerPixel);
            switch (format)
            {
                case "DXT1":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 8;
                    else
                        return totalBytes;
                case "DXT5":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 16;
                    else
                        return totalBytes;
                case "V8U8":
                case "G8":
                case "A8R8G8B8":
                    return totalBytes;
                default:
                    throw new FormatException("Invalid image format");
            }
        }

        private long ImageMipMapDataSize(ImageSize imgsize, string format, float BytesPerPixel)
        {
            long totalBytes = (long)((float)(imgsize.width * imgsize.height) * BytesPerPixel);
            //Console.WriteLine("width: {0}, height: {1}, BPP: {2:F}, totalBytes: {3}", imgsize.width, imgsize.height, BytesPerPixel, totalBytes);
            switch (format)
            {
                case "DXT1":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 24;
                    else
                        return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                case "DXT5":
                    if (imgsize.width <= 4 && imgsize.height <= 4)
                        return 48;
                    else
                        return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                case "V8U8":
                    if (imgsize.width <= 2 && imgsize.height <= 2)
                        return 10;
                    else
                        return totalBytes + ImageMipMapDataSize(new ImageSize(imgsize.width / 2, imgsize.height / 2), format, BytesPerPixel);
                case "G8":
                case "A8R8G8B8":
                    return totalBytes;
                default:
                    throw new FormatException("Invalid image format");
            }
        }

        private byte[] ShrinkImage(byte[] imageData, ImageSize imgsize, float BytesPerPixel)
        {
            //Console.WriteLine("image length: {0}, image size: {1}, BPP: {2}", imageData.Length, imgsize, BytesPerPixel);
            byte[] final = new byte[imageData.Length / 4];
            int BPP = (int)BytesPerPixel;

            for (int i = 0; i < imgsize.height; i += 2)
            {
                for (int j = 0; j < imgsize.width; j += 2)
                {
                    for (int k = 0; k < BPP; k++)
                    {
                        final[(i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k] = (byte)
                          (((int)imageData[i * (imgsize.width * BPP) + j * BPP + k] +
                            (int)imageData[i * (imgsize.width * BPP) + j * BPP + BPP + k] +
                            (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + k] +
                            (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + BPP + k]) / 4);
                        /*if(imgsize == new ImageSize(8,8))
                            Console.WriteLine("pos {5}: {0} = ({1} + {2} + {3} + {4}) / 4 ",
                                final[(i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k],
                                (int)imageData[i * (imgsize.width * BPP) + j * BPP + k],
                                (int)imageData[i * (imgsize.width * BPP) + j * BPP + BPP + k],
                                (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + k],
                                (int)imageData[(i + 1) * (imgsize.width * BPP) + j * BPP + BPP + k],
                                (i * (imgsize.width * BPP) / 4) + j * BPP / 2 + k);*/
                    }
                }
            }

            return final;
        }
    }
}
